package com.jorado.apollo.service;

import com.ctrip.framework.apollo.openapi.client.constant.ApolloOpenApiConstants;
import com.ctrip.framework.apollo.openapi.client.exception.ApolloOpenApiException;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.*;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.Map;

abstract class AbstractApiService {
    private static final Escaper pathEscaper = UrlEscapers.urlPathSegmentEscaper();
    private static final Escaper queryParamEscaper = UrlEscapers.urlFormParameterEscaper();

    private final String baseUrl;

    protected final CloseableHttpClient client;
    protected final BasicCookieStore cookieStore;
    protected final Gson gson;

    AbstractApiService(CloseableHttpClient client, String baseUrl) {
        this(client, null, baseUrl);
    }

    AbstractApiService(CloseableHttpClient client, BasicCookieStore cookieStore, String baseUrl) {
        this.client = client;
        this.baseUrl = baseUrl;
        this.gson = new GsonBuilder().setDateFormat(ApolloOpenApiConstants.JSON_DATE_FORMAT).create();
        this.cookieStore = cookieStore;
    }

    protected CloseableHttpResponse get(String path) throws IOException {
        HttpGet get = new HttpGet(String.format("%s/%s", baseUrl, path));

        return execute(get);
    }

    protected CloseableHttpResponse post(String path, Object entity) throws IOException {
        HttpPost post = new HttpPost(String.format("%s/%s", baseUrl, path));

        return execute(post, entity);
    }

    protected CloseableHttpResponse post(String path, Map entity) throws IOException {
        HttpPost post = new HttpPost(String.format("%s/%s", baseUrl, path));

        return execute(post, entity);
    }

    protected CloseableHttpResponse put(String path, Object entity) throws IOException {
        HttpPut put = new HttpPut(String.format("%s/%s", baseUrl, path));

        return execute(put, entity);
    }

    protected CloseableHttpResponse delete(String path) throws IOException {
        HttpDelete delete = new HttpDelete(String.format("%s/%s", baseUrl, path));

        return execute(delete);
    }

    protected String escapePath(String path) {
        return pathEscaper.escape(path);
    }

    protected String escapeParam(String param) {
        return queryParamEscaper.escape(param);
    }

    private CloseableHttpResponse execute(HttpEntityEnclosingRequestBase requestBase, Object entity) throws IOException {
        requestBase.setEntity(new StringEntity(gson.toJson(entity), ContentType.APPLICATION_JSON));

        return execute(requestBase);
    }

    private CloseableHttpResponse execute(HttpEntityEnclosingRequestBase requestBase, Map<String, Object> entity) throws IOException {
        String formData = "";
        for (Map.Entry<String, Object> entry : entity.entrySet()) {
            formData += entry.getKey() + "=" + entry.getValue();
            formData += "&";
        }
        requestBase.setEntity(new StringEntity(formData, ContentType.APPLICATION_FORM_URLENCODED));

        return execute(requestBase);
    }

    private CloseableHttpResponse execute(HttpUriRequest request) throws IOException {
        CloseableHttpResponse response = client.execute(request);

        checkHttpResponseStatus(response);

        return response;
    }

    private void checkHttpResponseStatus(HttpResponse response) {
        if (response.getStatusLine().getStatusCode() == 200 || response.getStatusLine().getStatusCode() == 302) {
            return;
        }

        StatusLine status = response.getStatusLine();
        String message = "";
        try {
            message = EntityUtils.toString(response.getEntity());
        } catch (IOException e) {
            //ignore
        }

        throw new ApolloOpenApiException(status.getStatusCode(), status.getReasonPhrase(), message);
    }

    protected void checkNotEmpty(String value, String name) {
        Preconditions.checkArgument(!Strings.isNullOrEmpty(value), name + " should not be null or empty");
    }
}